在 Go 版的 Mongory,C 的記憶體池(memory pool)與 Go 的 GC 必須協同工作:既要確保不洩漏,也要避免過早釋放。本篇整理生命週期設計、runtime.SetFinalizer
的使用方式、以及可驗證的測試思路
runtime.SetFinalizer
在 Go 邊界上兜住遺漏的 Free
呼叫,避免長生物件在 mongory-go/matcher.go
中,為 cgo.Matcher 掛上 finalizer:
func NewCMatcher(condition map[string]any, context *any) (CMatcher, error) {
matcher, err := cgo.NewMatcher(condition, context)
if err != nil { return nil, err }
runtime.SetFinalizer(matcher, func(m *cgo.Matcher) { m.Free() })
return matcher, nil
}
Free()
,GC 最終會回收 Go 物件並觸發 finalizer,執行 C 資源釋放Free()
時機,finalizer 是保險,不應成為釋放的唯一路徑記憶體池統一管理跨界 handle,Reset/Free
會刪除所有 handle,避免洩漏:
type MemoryPool struct {
CPoint *C.mongory_memory_pool
handles []rcgo.Handle
}
...
func (m *MemoryPool) Reset() {
C.go_mongory_memory_pool_reset(m.CPoint)
for _, h := range m.handles { h.Delete() }
m.handles = m.handles[:0]
}
Reset()
:用於短期工作集(例如每次 Match
後),清理暫時性轉換資源Free()
:釋放整個 pool(通常在物件生命週期結束時)NewMatcher
會建立長期 pool 與 scratch pool,供重複匹配使用Match
結束後 scratch.Reset()
,保證後續呼叫不會積累垃圾matcher.Free()
,若遺漏,finalizer 兜底alloc/free
是否平衡Match
/Explain
/Trace
,觀察 RSS 與最終值runtime.GC()
與 runtime.KeepAlive()
控制可回收點,驗證 finalizer 會觸發且不二次釋放Free()
可重入(判空)或在呼叫端去重NewHandle
都必須被 trackHandle
,並隨 Reset/Free
刪除C 的記憶體池讓配置高效,而 Go 的 GC 讓使用便捷。兩者交界的關鍵是「明確的所有權」與「集中回收」:以 MemoryPool 為核心,結合 Reset/Free
與 SetFinalizer
作為最後保險,讓生命週期可預期、可驗證